package cn.xzqwjw.taskmanager.security;

import cn.xzqwjw.taskmanager.common.customEnum.ResponseCodeEnum;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;

/**
 * 权限控制策略管理，即拿用户所拥有的角色和权限 和 要访问的url所需要的角色权限进行比对
 *
 * @author rush
 */
@Component
public class AuthAccessDecisionManager implements AccessDecisionManager {

  @Override
  public void decide(Authentication auth, Object o, Collection<ConfigAttribute> cas)
      throws AccessDeniedException, InsufficientAuthenticationException {
    // 如果这里返回空说明前面的 authMetadataSource 返回空值
    // 说明是 login/logout 的url
    if (CollectionUtils.isEmpty(cas)) {
      return;
    }
    // 定义一个字符串用来存储访问某个url所需要的角色或者权限
    String needRole;
    // 循环遍历cas获取访问当前url所需要的角色和权限
    for (ConfigAttribute ca : cas) {
      needRole = ca.getAttribute();
      // 先判断是否是没有匹配到权限的路径
      // "ROLE_NEED_LOGIN"是前面自定义的默认的角色
      if ("ROLE_NEED_LOGIN".equals(needRole)) {
        // 说明要访问的url还没有定义需要角色和权限才能访问
        // 或者说要访问的url不需要角色和权限就能访问
        if (auth instanceof AnonymousAuthenticationToken) {
          // 如果是匿名用户，那么不允许访问，抛出需要登录的异常
          throw new BadCredentialsException(ResponseCodeEnum.UNAUTHORIZED.getMessage());
        } else {
          // 这里通常就是指用户访问"/login"或者"/logout"之类的事先定义过的不需要角色权限的地址
          return;
        }
      }
      // 执行到这里，说明请求访问的路径是需要角色和权限的
      // 那么首先应获取当前用户经过登录后得到的所拥有的角色和权限
      Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
      for (GrantedAuthority authority : authorities) {
        if (authority.getAuthority().equals(needRole)) {
          // 比对成功，说明已登录且具有相对应的角色和权限，正常放行，允许访问
          return;
        }
        // 如果有 SUPERADMIN 的角色，也是正常放行
        if ("ROLE_SUPERADMIN".equals(authority.getAuthority())) {
          return;
        }
      }
    }
    // 执行到这里，说明权限不足，抛出403异常
    throw new AccessDeniedException(ResponseCodeEnum.FORBIDDEN.getMessage());
  }

  /**
   * 这个方法在启动的时候被 AbstractSecurityInterceptor 调用
   * 来决定 AccessDecisionManager 是否可以执行传递 ConfigAttribute
   */
  @Override
  public boolean supports(ConfigAttribute configAttribute) {
    return true;
  }

  /**
   * 这个方法被安全拦截器实现调用
   * 包含安全拦截器将显示的 AccessDecisionManager 支持安全对象的类型
   */
  @Override
  public boolean supports(Class<?> aClass) {
    return true;
  }

}
